#include <unistd.h>
#include <string.h>

#include <arpa/inet.h>

#include "net_context.h"
#ifndef NATIVE_SOCKET
#include "utils.h"
#endif

using namespace usnet;

std::mutex NetContext::mtx_ { };
NetContext* NetContext::net_ctx_ { nullptr };

#ifndef NATIVE_SOCKET
bool NetContext::Init()
{   
    std::lock_guard<std::mutex> lock { mtx_ };
    Destroy();
    net_ctx_ = (new(std::nothrow) NetContext { });
    if (net_ctx_ == nullptr) {
        std::cerr << "failed to new NetContex instance()" << std::endl;
        return false;
    }
    if (net_ctx_->InitIfconfig() ==  false) {
        std::cerr << "failed to get local network interface configuration" << std::endl;
        return false;
    }

    net_ctx_->dump_fp_ = fdopen(STDOUT_FILENO, "wb");

    return true;
}

bool NetContext::InitIfconfig()
{
    auto ctx = NetContext::GetContext();
    const char* cmd { "ifconfig" };
    FILE* cmd_output = popen(cmd, "r");
    if (cmd_output == nullptr) {
        LOG_ERROR(ctx, "failed to exec shell command ifconfig");
        return false;
    }
    char*  line { nullptr };
    size_t len { 0 };
    std::string ifname;
    uint32_t flags;
    uint32_t mtu;
    while (getline(&line, &len, cmd_output) > 0 ) {
        std::string str { line };
        str = StringTrim(str);
        if (str.length() == 0) {
            continue;
        }
        // LOG_DEBUG(ctx, "ifconfig line: %s.", str.c_str());
        auto slice_list = StringSplit(str);
        std::vector<std::string> str_list { };
        for (auto& slice : slice_list) {
            slice = StringTrim(slice);
            if (slice.length()) {
                str_list.push_back(slice);
            }
        }
        std::size_t size = str_list.size();
        if ((size == 4) && (str_list[2].compare("mtu") == 0)) {
            ifname = str_list[0].substr(0, str_list[0].length() - 1);
            str_list[1] = str_list[1].substr(6, str_list[1].length() - 1);
            flags = std::stoul(str_list[1]);
            mtu = std::stoul(str_list[3]);
            ifcfg_tab_.emplace(
                std::piecewise_construct,
                std::forward_as_tuple(ifname),
                std::forward_as_tuple(ifname, flags, mtu));
            // LOG_DEBUG(ctx, "ifconfig line: %s.", str.c_str());
            // LOG_DEBUG(ctx, "               ifname = %s, flags = %u, mtu = %u.", ifname.c_str(), flags, mtu);
            continue;
        }
        auto& ifcfg = ifcfg_tab_.at(ifname);
        if ((size >= 2) && (str_list[0].compare("inet") == 0))  {
            if (inet_pton(AF_INET, str_list[1].c_str(), &ifcfg.net_addr_.ip4_) != 1) {
                LOG_ERROR(ctx, "               failed to convert IPv4 address %s.", str_list[1].c_str());
                return false;
            }
            // LOG_DEBUG(ctx, "               IPv4 address %s.", str_list[1].c_str());
            if (size > 2) {
                if (inet_pton(AF_INET, str_list[3].c_str(), &ifcfg.net_addr_.ip4_netmask_) != 1) {
                    LOG_ERROR(ctx, "               failed to convert IPv4 netmask %s.", str_list[3].c_str());
                    return false;
                }
                // LOG_DEBUG(ctx, "               IPv4 netmask %s.", str_list[3].c_str());
            }
            if (size > 4) {
                if (inet_pton(AF_INET, str_list[5].c_str(), &ifcfg.net_addr_.ip4_broadcast_ ) != 1) {
                    LOG_ERROR(ctx, "               failed to convert IPv4 broadcast address %s.", str_list[5].c_str());
                    return false;
                }
                // LOG_DEBUG(ctx, "               IPv4 broadcast address %s.", str_list[5].c_str());
            }
            continue;
        }
        if ((size == 6) && (str_list[0].compare("inet6") == 0)) {
            if (inet_pton(AF_INET6, str_list[1].c_str(), &ifcfg.net_addr_.ip6_) != 1) {
                LOG_ERROR(ctx, "               failed to convert IPv6 address %s.", str_list[1].c_str());
                return false;
            }
            ifcfg.net_addr_.prefixlen_ = std::stoul(str_list[3]);
            ifcfg.net_addr_.scopeid_ = std::stoul(str_list[5], nullptr, 16);
            // LOG_DEBUG(
            //     ctx,
            //     "               IPv6 address %s, prefixlen = %u, scopeid = %u.",
            //     str_list[1].c_str(),
            //     ifcfg.net_addr_.prefixlen_,
            //     ifcfg.net_addr_.scopeid_);
            continue;
        }
        if ((size == 5) && (str_list[0].compare("ether") == 0)) {
            EthAddr2Num(ifcfg.eth_addr_, str_list[1].c_str());
            ifcfg.txqlen_ = std::stoul(str_list[3]);
            // LOG_DEBUG(ctx, "               eth address %s, txqueuelen = %u.", ifcfg.eth_addr_, ifcfg.txqlen_);
            continue;
        }
    }

    return true;
}

const Ifconfig* NetContext::GetIfconfig(const EndpointAddr* addr) const
{
    std::string ifname { };
    if (addr->inet_proto_ == InetProto::IPv4) {
        for (auto iter = ifcfg_tab_.begin(); iter != ifcfg_tab_.end(); iter++) {
            if (iter->second.net_addr_.ip4_ == addr->be_ip_.v4_) {
                ifname = iter->first;
                break;
            }
        }
    }
    if (addr->inet_proto_ == InetProto::IPv6) {
        for (auto iter = ifcfg_tab_.begin(); iter != ifcfg_tab_.end(); iter++) {
            if (ByteCompare(iter->second.net_addr_.ip6_, addr->be_ip_.v6_, 16) == 0) {
                ifname = iter->first;
                break;
            }
        }
    }
    if (ifname.empty()) {
        return nullptr;
    }
    return &ifcfg_tab_.at(ifname);
}
#else
bool NetContext::Init()
{   
    std::lock_guard<std::mutex> lock { mtx_ };
    Destroy();
    net_ctx_ = (new(std::nothrow) NetContext { });
    if (net_ctx_ == nullptr) {
        std::cerr << "failed to new NetContex instance()" << std::endl;
        return false;
    }
    return true;
}
#endif